package dbms.disk;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

import dbms.Constants;

/**
 * This is the lowest code of dbms-sjtu, it supplys some API for above to
 * operate the disk file.
 * 
 * @author weapon
 */
public class DiskManager {

	private String dbFileName;
	private File dbFile;
	private FileChannel dbFileChannel;
	private boolean isNew;

	/**
	 * Construct function, create a new file by the given file name.
	 * 
	 * @param the db file's name
	 */
	public DiskManager(String file) {
		if(file.length() > 4){
			if(!file.substring(file.length() - 4).equals(".sjk")){
				dbFileName = file + ".sjk";
			} else {
				dbFileName = file;
			}
		} else {
			dbFileName = file + ".sjk";
		}
		dbFileChannel = null;
		isNew = true;
		creatFile(dbFileName);
		openFile(dbFileName);
	}

	/**
	 * the db file name is like *.sjk(CS san jian ke) call the File
	 * createNewFile()
	 * 
	 * @param String
	 *            fileName
	 */
	public void creatFile(String fileName) {
		File diskFile = new File("database/" + fileName);
		if (diskFile.exists()) {
			isNew = false;
		} else {
			try {
				diskFile.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * call File delete()
	 * 
	 * @param the
	 *            file's name which will destroyed
	 */
	public void destroyFile(String fileName) {
		if(!fileName.substring(fileName.length() - 4).equals(".sjk")){
			fileName = fileName + ".sjk";
		}
		File delFile = new File("database/", fileName);
		if (!delFile.exists()) {
			System.out.println("This file is not exist, error.");
		} else {
			if (!delFile.delete()) {
				System.out.println("delete file " + fileName + ", error.");
			}
		}
	}

	public void openFile(String fileName) {
		dbFile = new File("database/", fileName);
		try {
			RandomAccessFile randomAccessFile = new RandomAccessFile(dbFile,
					"rws");
			dbFileChannel = randomAccessFile.getChannel();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			throw new RuntimeException("can not access file " + fileName);
		}
	}

	public void close(){
		try {
			dbFileChannel.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * use the class ByteBuffer, clear the buffer then read from db file to the buffer.
	 * synchronized : prevent another threads close this file
	 * @param pageNum : the page num of db file
	 * @param content : the buffer which will read to
	 */
	public synchronized void readPage(int pageNum, ByteBuffer content) {
		try {
			content.clear();
			dbFileChannel.read(content, pageNum * Constants.BLOCKSIZE);
		} catch (IOException e) {
			throw new RuntimeException("Can not read file" + dbFileName
					+ "'s content.");
		}
	}

	/**
	 * use rewind(), the position is set to zero.
	 * 
	 * @param pageNum : the page num of db file
	 * @param content : the buffer which will write to 
	 */
	public synchronized void writePage(int pageNum, ByteBuffer content) {
		try{
			content.rewind();
			dbFileChannel.write(content, pageNum * Constants.BLOCKSIZE);
		} catch (IOException e) {
			throw new RuntimeException("Can not write to file " + dbFileName);
		}
	}
	
	/**
	 * append a new page to database file's tail
	 * @param content: the content which will write to database file
	 * @return the new block number
	 */
	public synchronized int append(ByteBuffer content){
		int newBlk = size();
		writePage(newBlk, content);
		return newBlk;
	}

	/**
	 * delete specific page from database file, that mean we use autoVacuum
	 * @param pageNum
	 */
	public synchronized void deletePage(int pageNum) {
		//FIXME too much cost
	}
	
	public synchronized int size(){
		try {
			return (int)(dbFileChannel.size() / Constants.BLOCKSIZE);
		} catch (IOException e) {
			throw new RuntimeException("Can not access file " + dbFileName);
		}
	}
	
	public boolean isNew(){
		return isNew;
	}
	
	public String getFileName(){
		return this.dbFileName;
	}
}
